package com.uxsino.simo.indicator.retractor;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.uxsino.commons.utils.config.PropElement;
import com.uxsino.simo.indicator.Indicator;
import com.uxsino.simo.networkentity.EntityInfo;
import com.uxsino.simo.query.QueryContext;
import com.uxsino.simo.query.QueryTemplate;
import com.uxsino.simo.utils.ConfigLoadingContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;

/**
 * Extracts field value from a JSON string. In the method {@code retract}, the
 * second parameter is a JSON string, and the fields are read from it. <br />
 *
 * <retract parser="json_string" indicator="..."> See
 * {@link ValueRetractorFactory}
 * <field name="..." key_name="..." parser="..." pattern="..."/> etc, where name
 * is the one used in indicator definition, and key_name is the one used in the
 * JSON string, of the form {@code "AA.BB.CC.DD"} which gives the value in
 * {@code {"AA": {"BB": {"CC": {"DD": value, ...}, ...}, ...}, ...}} <br />
 * The default is that key_name = name.
 * 
 * 
 *
 */
public class JSONStringValueRetractor extends CompoundValueRetractor {
    private static Logger logger = LoggerFactory.getLogger(JSONStringValueRetractor.class);

    private Map<String, String> fieldMapper = new HashMap<>();

    /**
     * This allows extraction of values in JSONstring, and will definitely throw
     * {@link NullPointerException} if somehow the keys are malformed. <br />
     * The key is of the form {@code "AA.BB.CC.DD"} which gives the value in
     * {@code {"AA": {"BB": {"CC": {"DD": value, ...}, ...}, ...}, ...}}
     */
    @Override
    public Object doRetract(EntityInfo entity, QueryContext ctxt, QueryTemplate qt, Object obj) {
        if (null == obj) {
            logger.error("JSON string passed into retractor is null.");
            return null;
        }
        HashMap<String, Object> values = new HashMap<String, Object>(fieldRetrievers.size());
        JSONObject jsonObj = (obj instanceof JSONObject) ? (JSONObject) obj : JSON.parseObject((String) obj);
        for (Map.Entry<String, IValueRetractor> entry : fieldRetrievers.entrySet()) {
            Object value = null;
            String keyName = fieldMapper.get(entry.getKey());
            if (null == keyName) {
                logger.error("key_name should not be null for {} ", entry.getKey());
                continue;
            }
            if (keyName.contains("/")) {
                String[] jsonFlds = keyName.split("/");
                JSONObject jsonObjCurr = jsonObj;
                for (int i = 0; i < jsonFlds.length - 1; i++) {
                    if (jsonObjCurr == null) {
                        break;
                    }
                    String fieldKey = getPossibleFieldKey(jsonFlds[i], jsonObjCurr);
                    jsonObjCurr = jsonObjCurr.getJSONObject(fieldKey);
                }
                if (jsonObjCurr == null) {
                    logger.warn("no value for key_name={}", keyName);
                    continue;
                }
                value = entry.getValue().retract(entity, ctxt, qt,
                    jsonObjCurr.getString(getPossibleFieldKey(jsonFlds[jsonFlds.length - 1], jsonObjCurr)));
            } else {
                value = entry.getValue().retract(entity, ctxt, qt, jsonObj.getString(getPossibleFieldKey(keyName, jsonObj)));
            }
            values.put(entry.getKey(), value);
        }
        return values;
    }

    private String getPossibleFieldKey(String field, JSONObject jsonObj) {
        String[] keyArr = field.split("###");
        for (String key : keyArr) {
            if (jsonObj.containsKey(key)) {
                return key;
            }
        }
        return keyArr[0];
    }

    public void addFieldMap(String indicatorFieldName, String jsonFieldName) {
        fieldMapper.put(indicatorFieldName, jsonFieldName);
    }

    @Override
    public void loadProp(Indicator ind, PropElement eRetractor, ConfigLoadingContext lctxt) {
        super.loadProp(ind, eRetractor, lctxt);
        for (PropElement eFld : eRetractor.getElements("field")) {
            String fieldName = eFld.getProp("name");
            String jsonFieldName = eFld.getProp("key_name");

            if (jsonFieldName.isEmpty()) {
                jsonFieldName = fieldName;
            }
            addFieldMap(fieldName, jsonFieldName);

        }
    }
}
